package com.study.algorithm.datastruct.graph.impl;

import com.study.algorithm.datastruct.graph.GraphBasicOperate;
import com.study.algorithm.datastruct.graph.model.Edge;
import com.study.algorithm.datastruct.graph.model.Graph;
import com.study.algorithm.datastruct.graph.model.Vertex;

import java.util.*;
import java.util.stream.Collectors;

public class GraphBasicOperateImpl implements GraphBasicOperate {

    @Override
    public <T extends Collection> boolean batchInsertVertex(Graph graph, T vertices) {
        if (graph == null) {
            return false;
        }
        Set<Vertex> set = graph.getVertices();
        set.addAll(vertices);
        graph.removeRedundantVertice();
        return true;
    }

    @Override
    public <T extends Collection> boolean batchInsertEdge(Graph graph, T edges) {
        if (graph == null) {
            return false;
        }
        Set<Edge> set = graph.getEdges();
        set.addAll(edges);
        graph.removeRedundantEdge();
        return true;
    }

    @Override
    public boolean containVertex(Graph graph, Vertex vertex) {
        if (graph == null) {
            return false;
        }
        Set<Vertex> vertices = graph.getVertices();
        long matchCount = vertices
                .stream().filter(v -> v.getVertex().equals(vertex.getVertex()))
                .count();

        return matchCount != 0L;
    }


    @Override
    public Vertex containVertexAndReturnVertex(Graph graph, Vertex vertex) {
        if (graph == null) {
            return null;
        }
        Set<Vertex> vertices = graph.getVertices();
        List<Vertex> matchs = vertices
                .stream().filter(v -> v.getVertex().equals(vertex.getVertex())).collect(Collectors.toList());
        return matchs.size() > 0 ? matchs.get(0) : null;
    }

    @Override
    public void insertVertex(Graph graph, Vertex vertex) {
        if (containVertex(graph, vertex)) {
            return;
        }
        graph.getVertices().add(vertex);
    }

    @Override
    public boolean containEdge(Graph graph, Edge edge) {

        if (graph == null || edge == null) {
            return false;
        }

        String startVertex = edge.getStartVetex().getVertex();
        String endVertex = edge.getEndVetex().getVertex();

        Set<Edge> edges = graph.getEdges();
        long matchCount = edges
                .stream().filter(e -> {
                            String start = e.getStartVetex().getVertex();
                            String end = e.getEndVetex().getVertex();
                            return start.equals(startVertex) && end.equals(endVertex);
                        }
                ).count();
        return matchCount != 0L;
    }

    @Override
    public void insertOrUpdate(Graph graph, Edge edge) {
        if (graph == null || edge == null) {
            return;
        }
        Set<Edge> matchs = findEqualEdge(graph, edge);
        if (matchs == null || matchs.size() == 0) {
            graph.getEdges().add(edge);
        }
        int distance = edge.getDistance();
        //原则上只能有一个
        for (Edge edge1 : matchs) {
            edge1.setDistance(distance);
        }
    }

    @Override
    public boolean containNotUpdate(Graph graph, Edge edge) {
        if (graph == null || edge == null) {
            return false;
        }
        Set<Edge> matchs = findEqualEdge(graph, edge);
        if (matchs != null && matchs.size() != 0) {
            return false;
        }
        graph.getEdges().add(edge);
        return true;
    }

    @Override
    public boolean removeVertexAndEdge(Graph graph, Vertex vertex) {
        if (graph == null || vertex == null) {
            return false;
        }
        Set<Edge> matchs = findAllEdgeContainVertex(graph, vertex, true, true);
        if (matchs == null) {
            return true;
        }
        Set<Edge> edges = graph.getEdges();
        edges.removeAll(matchs);
        return true;
    }


    @Override
    public boolean removeEdge(Graph graph, Edge edge) {
        if (graph == null || edge == null) {
            return false;
        }
        Set<Edge> matchs = findEqualEdge(graph, edge);
        if (matchs == null) {
            return true;
        }
        Set<Edge> edges = graph.getEdges();
        edges.removeAll(matchs);
        return true;
    }

    @Override
    public Set<Edge> queryEdgesContainVertex(Graph graph, Vertex vertex) {
        if (graph == null || vertex == null) {
            return new HashSet<>(0);
        }
        return findAllEdgeContainVertex(graph, vertex, true, true);

    }

    @Override
    public Set<Edge> queryEdgesUsingTheEndVertex(Graph graph, Vertex vertex) {
        if (graph == null || vertex == null) {
            return new HashSet<>(0);
        }
        return findAllEdgeContainVertex(graph, vertex, false, true);

    }

    @Override
    public Set<Edge> queryEdgesUsingTheStartVertex(Graph graph, Vertex vertex) {
        if (graph == null || vertex == null) {
            return new HashSet<>(0);
        }
        return findAllEdgeContainVertex(graph, vertex, true, false);
    }

    private Set<Edge> findAllEdgeContainVertex(Graph graph, Vertex vertex,
                                               boolean containStartVertex,
                                               boolean containEndVertex) {
        if (!containEndVertex && !containStartVertex) {
            return new HashSet<>(0);
        }
        Set<Edge> edges = graph.getEdges();
        String vertex1 = vertex.getVertex();
        Set<Edge> matchs = edges
                .stream().filter(e -> {
                            String start = e.getStartVetex().getVertex();
                            String end = e.getEndVetex().getVertex();
                            return (containStartVertex && vertex1.equals(start)) ||
                                    (containEndVertex && vertex1.equals(end));
                        }
                ).collect(Collectors.toSet());
        return matchs;
    }


    @Override
    public Edge getEdgeThroughStartAndEndVertex(Graph graph, Vertex startVertex, Vertex endVertex) {
        Set<Vertex> endVerties = new HashSet<>(1);
        endVerties.add(endVertex);
        Set<Vertex> startVerties = new HashSet<>(1);
        startVerties.add(startVertex);
        Set<Edge> match = queryEdgesUsingStartVertiesAndEndVerties(graph, startVerties, endVerties);
        Iterator<Edge> it = match.iterator();
        return it.hasNext() ? it.next() : null;
    }

    @Override
    public Set<Edge> queryEdgesUsingStartVertiesAndEndVetex(Graph graph, Set<Vertex> startVerties, Vertex startVertex) {
        Set<Vertex> endVerties = new HashSet<>(1);
        endVerties.add(startVertex);
        return queryEdgesUsingStartVertiesAndEndVerties(graph, startVerties, endVerties);
    }


    private Set<Edge> findEqualEdge(Graph graph, Edge edge) {
        String startVertex = edge.getStartVetex().getVertex();
        String endVertex = edge.getEndVetex().getVertex();
        Set<Edge> edges = graph.getEdges();
        Set<Edge> matchs = edges
                .stream().filter(e -> {
                            String start = e.getStartVetex().getVertex();
                            String end = e.getEndVetex().getVertex();
                            return start.equals(startVertex) && end.equals(endVertex);
                        }
                ).collect(Collectors.toSet());
        return matchs;
    }


    /**
     * @param graph
     * @param startVerties 开始顶点集合
     * @param endVerties   结束顶点集合
     * @return 返回起始顶点在开始顶点集合并且结束顶点在结束顶点集合的边
     */
    @Override
    public Set<Edge> queryEdgesUsingStartVertiesAndEndVerties(Graph graph, Set<Vertex> startVerties, Set<Vertex> endVerties) {


        Set<Edge> edges = graph.getEdges();
        Set<String> startStr = startVerties.stream().map(v -> v.getVertex()).collect(Collectors.toSet());
        Set<String> endStr = endVerties.stream().map(v -> v.getVertex()).collect(Collectors.toSet());
        Set<Edge> matchs = edges
                .stream().filter(e -> {
                            String start = e.getStartVetex().getVertex();
                            String end = e.getEndVetex().getVertex();
                            return startStr.contains(start) && endStr.contains(end);
                        }
                ).collect(Collectors.toSet());
        return matchs;
    }
}
